#include "stdafx.h"
#include "DDSTextureLoader.h"
#include "ContentManager.h"
#include <d3d11.h>
#include <d3dcompiler.h>
#include <fstream>
#include <assert.h>
#include <guiddef.h>
#include "..\EdgeGraphics\VertexFormats.h"
#include "..\EdgeGraphics\VertexBuffer.h"
#include "..\EdgeGraphics\Model.h"
#include "..\EdgeGraphics\GraphicsManager.h"
#include "..\EdgeGraphics\ShaderContainer.h"
#include "..\EdgeGraphics\Material.h"
#include "..\EdgeAudio\AudioManager.h"
#include "..\EdgeGraphics\Texture2D.h"
#include "..\EdgeGraphics\TextureCube.h"
#include "..\EdgeGraphics\VertexBuffer.h"
#include <fmod.h>
#include <fmod.hpp>

using namespace std;
using namespace VertexFormats;

/*static*/ContentManager* ContentManager::instance = nullptr;

ContentManager::ContentManager(const char* contentDirectory)
{
	this->contentDirectory = contentDirectory;
}

ContentManager::~ContentManager()
{

}



/*static*/ void ContentManager::Shutdown(void)
{
	delete instance;
}

/*static*/ void ContentManager::Init(const char* contentDirectory)
{
	instance = new ContentManager(contentDirectory);
}

/*static*/ ContentManager* ContentManager::GetInstance(void)
{
	return instance;
}

Model* ContentManager::LoadMdl(const char* filename, bool dynamic)
{
	VertexBuffer* outval = LoadVertexBuffer(filename, dynamic);

	return new Model(outval);
}

VertexBuffer* ContentManager::LoadVertexBuffer(const char* filename, bool dynamic)
{
	fstream file(filename, ios::binary | ios::in);
	assert(file.is_open() && "Failed to open file!");
	// read in buffer size
	unsigned int vertexBufferCount;
	file.read(reinterpret_cast<char*>(&vertexBufferCount), sizeof(unsigned int));
	// read in vertex buffer
	VertexNormalTextureTangent* vertecies = new VertexNormalTextureTangent[vertexBufferCount];
	file.read(reinterpret_cast<char*>(vertecies), vertexBufferCount * sizeof(VertexNormalTextureTangent));
	// read in index buffer size
	unsigned int indexBufferCount;
	file.read(reinterpret_cast<char*>(&indexBufferCount), sizeof(unsigned int));
	// read in index buffer
	unsigned int* indexBuffer = new unsigned int[indexBufferCount];
	file.read(reinterpret_cast<char*>(indexBuffer), sizeof(unsigned int) * indexBufferCount);
	// close file
	file.close();


	// create vertex buffer
	VertexBuffer* outval = new VertexBuffer(vertecies, indexBuffer, vertexBufferCount, indexBufferCount, VERTEX_TYPE_POSITION_TEXCOORD_NORMAL_TANGENT, dynamic);
	delete[] vertecies;
	delete[] indexBuffer;
	return outval;
}

wchar_t* convertToUnicode(const char* ascii)
{
	int length = strlen(ascii);
	wchar_t* outval = new wchar_t[length + 1];
	for (int i = 0; i < length; i++)
		outval[i] = wchar_t(*(ascii + i));

	outval[length] = '\0';

	return outval;
}

ShaderContainer* ContentManager::LoadShaderPair(const char* vertexShaderFilename, const char* pixelShaderFilename, const char* geometryShaderFilename)
{
#pragma warning(disable: 4244) 
	// store  a pointer to graphics manager
	GraphicsManager* graphics = GraphicsManager::GetInstance();

	// load vertex shader data
	ifstream vShaderFile(vertexShaderFilename, ifstream::in | ifstream::binary);
	assert(vShaderFile.is_open() && !vShaderFile.fail() && "Failed to open vertex shader file");
	vShaderFile.seekg(0, ifstream::end);
	unsigned int vShaderFile_length = vShaderFile.tellg();
	vShaderFile.seekg(0, ifstream::beg);
	char* vShaderFile_data = new char[vShaderFile_length];
	vShaderFile.read(vShaderFile_data, vShaderFile_length);
	vShaderFile.close();

	// load pixel shader data
	ifstream pShaderFile(pixelShaderFilename, ifstream::in | ifstream::binary);
	assert(pShaderFile.is_open() && !pShaderFile.fail() && "Failed to open pixel shader file");
	pShaderFile.seekg(0, ios::end);
	unsigned int pShaderFile_length = pShaderFile.tellg();
	pShaderFile.seekg(0, ios::beg);
	char* pShaderFile_data = new char[pShaderFile_length];
	pShaderFile.read(pShaderFile_data, pShaderFile_length);
	pShaderFile.close();

	char* gShaderFile_data = nullptr;
	unsigned int gShaderFile_length = 0;
	// load geometry shader data, if we have one
	if (geometryShaderFilename)
	{
		ifstream gShaderFile(geometryShaderFilename, ifstream::in | ifstream::binary);
		assert(gShaderFile.is_open() && !gShaderFile.fail() && "Failed to open geometry shader file");
		gShaderFile.seekg(0, ios::end);
		gShaderFile_length = gShaderFile.tellg();
		gShaderFile.seekg(0, ios::beg);
		gShaderFile_data = new char[gShaderFile_length];
		gShaderFile.read(gShaderFile_data, gShaderFile_length);
		gShaderFile.close();
	}

	// Create shaders
	ID3D11VertexShader* vShader = graphics->createVertexShader(vShaderFile_data, vShaderFile_length);
	assert(vShader && "Failed to create vertex shader");
	ID3D11PixelShader* pShader = graphics->createPixelShader(pShaderFile_data, pShaderFile_length);
	assert(pShader && "Failed to create pixel shader");
	ID3D11GeometryShader* gShader = nullptr;
	if (geometryShaderFilename)
	{
		gShader = graphics->createGeometryShader(gShaderFile_data, gShaderFile_length);
		assert(gShader && "Failed to create geometry shader");
	}

	// create the new shader container and return it
	ShaderContainer* outval = new ShaderContainer(vShader, pShader);
	if (geometryShaderFilename)
		outval->setGeometryShader(gShader);

	// free temp vars
	delete[] pShaderFile_data;
	delete[] vShaderFile_data;
	if (geometryShaderFilename)
		delete[] gShaderFile_data;
	return outval;
}

ShaderContainer* ContentManager::LoadShaders(const char* vertexShaderFilename, const char* pixelShaderFilename, const char* geometryShaderFilename, const char* hullShaderFilename, const char* domainShaderFilename)
{
#pragma warning(disable: 4244) 
	// store  a pointer to graphics manager
	GraphicsManager* graphics = GraphicsManager::GetInstance();

	// load vertex shader data
	ifstream vShaderFile(vertexShaderFilename, ifstream::in | ifstream::binary);
	assert(vShaderFile.is_open() && !vShaderFile.fail() && "Failed to open vertex shader file");
	vShaderFile.seekg(0, ifstream::end);
	unsigned int vShaderFile_length = vShaderFile.tellg();
	vShaderFile.seekg(0, ifstream::beg);
	char* vShaderFile_data = new char[vShaderFile_length];
	vShaderFile.read(vShaderFile_data, vShaderFile_length);
	vShaderFile.close();

	// load pixel shader data
	ifstream pShaderFile(pixelShaderFilename, ifstream::in | ifstream::binary);
	assert(pShaderFile.is_open() && !pShaderFile.fail() && "Failed to open pixel shader file");
	pShaderFile.seekg(0, ios::end);
	unsigned int pShaderFile_length = pShaderFile.tellg();
	pShaderFile.seekg(0, ios::beg);
	char* pShaderFile_data = new char[pShaderFile_length];
	pShaderFile.read(pShaderFile_data, pShaderFile_length);
	pShaderFile.close();

	char* hShaderFile_data = nullptr;
	unsigned int hShaderFile_length = 0;
	// load hull shader data, if any
	if (hullShaderFilename)
	{
		ifstream hShaderFile(geometryShaderFilename, ifstream::in | ifstream::binary);
		assert(hShaderFile.is_open() && !hShaderFile.fail() && "Failed to open geometry shader file");
		hShaderFile.seekg(0, ios::end);
		hShaderFile_length = hShaderFile.tellg();
		hShaderFile.seekg(0, ios::beg);
		hShaderFile_data = new char[hShaderFile_length];
		hShaderFile.read(hShaderFile_data, hShaderFile_length);
		hShaderFile.close();
	}

	char* dShaderFile_data = nullptr;
	unsigned int dShaderFile_length = 0;
	// load domain shader data, if any
	if (domainShaderFilename)
	{
		ifstream dShaderFile(geometryShaderFilename, ifstream::in | ifstream::binary);
		assert(dShaderFile.is_open() && !dShaderFile.fail() && "Failed to open geometry shader file");
		dShaderFile.seekg(0, ios::end);
		dShaderFile_length = dShaderFile.tellg();
		dShaderFile.seekg(0, ios::beg);
		dShaderFile_data = new char[dShaderFile_length];
		dShaderFile.read(dShaderFile_data, dShaderFile_length);
		dShaderFile.close();
	}

	char* gShaderFile_data = nullptr;
	unsigned int gShaderFile_length = 0;
	// load geometry shader data, if we have one
	if (geometryShaderFilename)
	{
		ifstream gShaderFile(geometryShaderFilename, ifstream::in | ifstream::binary);
		assert(gShaderFile.is_open() && !gShaderFile.fail() && "Failed to open geometry shader file");
		gShaderFile.seekg(0, ios::end);
		gShaderFile_length = gShaderFile.tellg();
		gShaderFile.seekg(0, ios::beg);
		gShaderFile_data = new char[gShaderFile_length];
		gShaderFile.read(gShaderFile_data, gShaderFile_length);
		gShaderFile.close();
	}

	// Create shaders
	ID3D11VertexShader* vShader = graphics->createVertexShader(vShaderFile_data, vShaderFile_length);
	assert(vShader && "Failed to create vertex shader");
	ID3D11PixelShader* pShader = graphics->createPixelShader(pShaderFile_data, pShaderFile_length);
	assert(pShader && "Failed to create pixel shader");
	ID3D11GeometryShader* gShader = nullptr;
	if (geometryShaderFilename)
	{
		gShader = graphics->createGeometryShader(gShaderFile_data, gShaderFile_length);
		assert(gShader && "Failed to create geometry shader");
	}
	// create hull shader
	ID3D11HullShader* hShader = nullptr;
	if (hullShaderFilename)
	{
		graphics->getDevice()->CreateHullShader((const void*)hShaderFile_data, hShaderFile_length, nullptr, &hShader);
		assert(hShader && "Failed to create hull shader");
	}
	// create domain shader
	ID3D11DomainShader* dShader = nullptr;
	if (domainShaderFilename)
	{
		graphics->getDevice()->CreateDomainShader((const void*)dShaderFile_data, dShaderFile_length, nullptr, &dShader);
		assert(dShader && "Failed to create domain shader");
	}

	// create the new shader container and return it
	ShaderContainer* outval = new ShaderContainer(vShader, pShader);
	if (geometryShaderFilename)
		outval->setGeometryShader(gShader);
	if (hullShaderFilename)
		outval->setHullShader(hShader);
	if (domainShaderFilename)
		outval->setDomainShader(dShader);

	// free temp vars
	delete[] pShaderFile_data;
	delete[] vShaderFile_data;
	if (geometryShaderFilename)
		delete[] gShaderFile_data;
	if (hullShaderFilename)
		delete[] hShaderFile_data;
	if (domainShaderFilename)
		delete[] dShaderFile_data;
	return outval;
}

// represents a color value on the disk
struct BinaryColor
{
	char R, G, B, A;
};

// represents a color sutiable for use in direct x
struct DXColor
{
	float R, G, B, A;

	DXColor(void)
	{
		memset(this, 0, 4);
	}
	DXColor(BinaryColor& other)
	{
		this->R = float(other.R) / 255.f;
		this->G = float(other.G) / 255.f;
		this->B = float(other.B) / 255.f;
		this->A = float(other.A) / 255.f;
	}
};

ID3D11ShaderResourceView* makeTexture(DXColor* data, int width, int height)
{

	// description of the texture
	D3D11_TEXTURE2D_DESC texture_desc;
	ZeroMemory(&texture_desc, sizeof(D3D11_TEXTURE2D_DESC));

	texture_desc.ArraySize = 1;
	texture_desc.MipLevels = 1;
	texture_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
	texture_desc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
	texture_desc.Width = width;
	texture_desc.Height = height;
	texture_desc.Usage = D3D11_USAGE_DEFAULT;
	texture_desc.SampleDesc.Count = 1;

	// subresource data for the texture
	D3D11_SUBRESOURCE_DATA subresource;
	ZeroMemory(&subresource, sizeof(D3D11_SUBRESOURCE_DATA));

	subresource.pSysMem = data;
	subresource.SysMemPitch = sizeof(DXColor) * width;
	subresource.SysMemSlicePitch = (width * height) * sizeof(DXColor);

	// create direct x texture
	ID3D11Texture2D* texture;
	GraphicsManager::GetInstance()->getDevice()->CreateTexture2D(&texture_desc, &subresource, &texture);

	D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc;
	ZeroMemory(&srv_desc, sizeof(D3D11_SHADER_RESOURCE_VIEW_DESC));

	srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
	srv_desc.Texture2D.MipLevels = 1;

	ID3D11ShaderResourceView* srv = GraphicsManager::GetInstance()->createSRV(&srv_desc, texture);
	return srv;
}

void makeTextureFromFile(fstream* file, Texture2D** out_texture, DXGI_FORMAT format = DXGI_FORMAT_R8G8B8A8_UNORM, D3D11_BIND_FLAG bindFlags = D3D11_BIND_SHADER_RESOURCE, D3D11_CPU_ACCESS_FLAG cpu_access = D3D11_CPU_ACCESS_WRITE, D3D11_USAGE usage = D3D11_USAGE_DYNAMIC)
{
	// read width and height
	int width, height;
	file->read(reinterpret_cast<char*>(&width), sizeof(int));
	file->read(reinterpret_cast<char*>(&height), sizeof(int));

	// create color array
	BinaryColor* colorInfo = new BinaryColor[width * height];
	// read color data
	for (int i = 0; i < width * height; i++)
	{
		file->read(&colorInfo[i].A, 1);
		file->read(&colorInfo[i].R, 1);
		file->read(&colorInfo[i].G, 1);
		file->read(&colorInfo[i].B, 1);
	}

	*out_texture = new Texture2D(width, height, format, nullptr, bindFlags, cpu_access, usage);
	(*(out_texture))->setData(colorInfo, sizeof(BinaryColor));

	delete colorInfo;
}

Material* ContentManager::LoadMaterial(const char* filename)
{
	// open filename
	fstream file(filename, ios::in | ios::binary);
	assert(file.is_open() && "Failed to open texture!");
	
	Texture2D* diffuse = nullptr;
	Texture2D* specular = nullptr;
	Texture2D* normal = nullptr;
	makeTextureFromFile(&file, &diffuse);

	bool hasNormalMap = false;
	file.read(reinterpret_cast<char*>(&hasNormalMap), 1);
	// read normal map data, if any
	if (hasNormalMap)
		makeTextureFromFile(&file, &normal);

	bool hasSpecularMap = false;
	file.read(reinterpret_cast<char*>(&hasSpecularMap), 1);
	// read specular data, if any
	if (hasSpecularMap)
		makeTextureFromFile(&file, &specular);

	// close the file
	file.close();


	// create outvalue
	Material* outval = new Material();
	outval->textures.diffuseMap = diffuse;
	outval->textures.specularMap = specular;
	outval->textures.normalMap = normal;


	return outval;
}

Texture2D* ContentManager::LoadTexture(const char* filename, float* out_width, float* out_height, Vector4* transparent_color)
{
	fstream file(filename, ios::in | ios::binary);

	Texture2D* outval = nullptr;
	makeTextureFromFile(&file, &outval);

	// close the file
	file.close();
	
	return outval;
}

#define fourccRIFF 'FFIR'
#define fourccDATA 'atad'
#define fourccFMT ' tmf'
#define fourccWAVE 'EVAW'
#define fourccXWMA 'AMWX'
#define fourccDPDS 'sdpd'

HRESULT FindChunk(HANDLE hFile, DWORD fourcc, DWORD& dwChunkSize, DWORD& dwChunkDataPosition)
{
	HRESULT hr = S_OK;
	if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, 0, NULL, FILE_BEGIN))
		return HRESULT_FROM_WIN32(GetLastError());

	DWORD dwChunkType;
	DWORD dwChunkDataSize;
	DWORD dwRIFFDataSize = 0;
	DWORD dwFileType;
	DWORD bytesRead = 0;
	DWORD dwOffset = 0;

	while (hr == S_OK)
	{
		DWORD dwRead;
		if (0 == ReadFile(hFile, &dwChunkType, sizeof(DWORD), &dwRead, NULL))
			hr = HRESULT_FROM_WIN32(GetLastError());

		if (0 == ReadFile(hFile, &dwChunkDataSize, sizeof(DWORD), &dwRead, NULL))
			hr = HRESULT_FROM_WIN32(GetLastError());

		switch (dwChunkType)
		{
		case fourccRIFF:
			dwRIFFDataSize = dwChunkDataSize;
			dwChunkDataSize = 4;
			if (0 == ReadFile(hFile, &dwFileType, sizeof(DWORD), &dwRead, NULL))
				hr = HRESULT_FROM_WIN32(GetLastError());
			break;

		default:
			if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, dwChunkDataSize, NULL, FILE_CURRENT))
				return HRESULT_FROM_WIN32(GetLastError());
		}

		dwOffset += sizeof(DWORD) * 2;

		if (dwChunkType == fourcc)
		{
			dwChunkSize = dwChunkDataSize;
			dwChunkDataPosition = dwOffset;
			return S_OK;
		}

		dwOffset += dwChunkDataSize;

		if (bytesRead >= dwRIFFDataSize) return S_FALSE;

	}

	return S_OK;

}

HRESULT ReadChunkData(HANDLE hFile, void * buffer, DWORD buffersize, DWORD& bufferoffset)
{
	HRESULT hr = S_OK;
	if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, bufferoffset, NULL, FILE_BEGIN))
		return HRESULT_FROM_WIN32(GetLastError());
	DWORD dwRead;
	if (0 == ReadFile(hFile, buffer, buffersize, &dwRead, NULL))
		hr = HRESULT_FROM_WIN32(GetLastError());
	return hr;
}

SoundEffect2D* ContentManager::LoadSoundEffect2D(const char* filename)
{
	return AudioManager::GetInstance()->CreateSound2D(filename);
}

SoundEffect3D* ContentManager::LoadSoundEffect3D(const char* filename)
{
	return AudioManager::GetInstance()->CreateSound3D(filename);
}

TextureCube* ContentManager::LoadTextureCube(const wchar_t* filename)
{
	ID3D11Resource* resource;
	ID3D11ShaderResourceView* srv;
	CreateDDSTextureFromFile(GraphicsManager::GetInstance()->getDevice(), filename, &resource, &srv);

	return new TextureCube((ID3D11Texture2D*)resource, srv);
}